"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinkedMap = exports.Map = exports.MapType = void 0;
/**
 * @ Author: Yoann Meclot. MSay2
 * @ Created on: 2020-06-04 05:54:17
 * @ Modified on: 2020-09-27 01:54:00
 *
 * @version 1.8
 */
/**
 * Copyright (c) 2020 MSay2, Yoann Meclot - LinkedMap.ts
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const Argument_js_1 = require("./Argument.js");
const Exceptions_js_1 = require("./Exceptions.js");
const Utils_js_1 = require("./Utils.js");
const StringBuilder_js_1 = require("./StringBuilder.js");
/**
 * The Entry class is a mean to log a key associated with its value.
 *
 * The Entry class is used by the EntryIterable class to obtain each entry to the map
 * and thus obtain each keys and values in a loop.
 *
 * @class Entry<K,V>
 * @version 1.2
 * @since 1.4
 */
class Entry {
}
/**
 * The EntryIterable class is a mean to reiterate the entries to the map.
 *
 * It allows to you obtain eachs values and eachs keys of eachs entries of the map
 * with its respectives methods (#method Entry.getKey) and (#method Entry.getValue).
 *
 * @class EntryIterable
 * @implements {Iterable}
 * @version 1.1
 * @since 1.5
 */
class EntryIterable {
    constructor(keys, values, size) {
        this.keys = [];
        this.values = [];
        this.index = -1;
        this.size = 0;
        this.size = size;
        this.keys = keys;
        this.values = values;
    }
    [Symbol.iterator]() {
        return {
            next: () => {
                this.index++;
                if (this.index < this.size) {
                    return {
                        value: {
                            getKey: () => this.keys[this.index],
                            getValue: () => this.values[this.index],
                            isLastData: () => {
                                if (this.index == this.size - 1)
                                    return true;
                                return false;
                            },
                            isFirstData: () => {
                                if (this.index == 0)
                                    return true;
                                return false;
                            }
                        },
                        done: false
                    };
                }
                return {
                    value: null,
                    done: true
                };
            }
        };
    }
}
/**
 * Public constants for type of inversement of the map.
 *
 * @class MapType
 * @version 1.0
 * @since 1.0
 */
class MapType {
}
exports.MapType = MapType;
/**
 * Reverse only keys
 *
 * @constant TYPE_KEYS
 * @type {number}
 * @since 1.0
 */
MapType.TYPE_KEYS = -1;
/**
 * Reverse only values
 *
 * @constant TYPE_VALUES
 * @type {number}
 * @since 1.0
 */
MapType.TYPE_VALUES = -2;
/**
 * The LinkedMap tool allows you to manipulate a map containing keys and values.
 *
 * LinkedMap is a way to create and manage a associative array.
 *
 * @class Map
 * @version 1.5
 * @since 1.0
 */
class Map {
}
exports.Map = Map;
/**
 * The LinkedMap tool allows you to manipulate a map containing keys and values.
 *
 * LinkedMap is a way to create and manage a associative array.
 *
 * @class LinkedMap
 * @version 1.5
 * @since 1.0
 */
class LinkedMap extends Map {
    constructor() {
        super();
        this.keys = [];
        this.values = [];
        this.size = 0;
        this.PREFIX_EXCEPTION = "LinkedMap: ";
        this.MAXIMUM_CAPACITY = 1 << 30;
    }
    /**
     * Init a new object Map of your object.
     *
     * @param object Your primitive object.
     * @returns Return the newest map.
     * @type {Map}
     * @since 1.7
     */
    static fromObject(object) {
        let obj = object;
        if (obj == null) {
            return new LinkedMap();
        }
        let map = new LinkedMap();
        for (let key in obj) {
            map.add(key, obj[key]);
        }
        obj = null;
        return map;
    }
    length() {
        return this.size;
    }
    contains(value) {
        return this.indexOf(value) >= 0;
    }
    keyOf(value) {
        let index = this.indexOf(value);
        if (index >= 0) {
            for (let i = 0; i < this.size; i++) {
                if (index == i) {
                    return this.keys[i];
                }
            }
        }
        return null;
    }
    add(key, value) {
        if (this.size >= this.MAXIMUM_CAPACITY) {
            throw new Exceptions_js_1.MaximumCapacityException(this.PREFIX_EXCEPTION + "You have reached the maximum capacity of elements of the map. You have not permission to add a new value in the map.");
        }
        if (!Argument_js_1.Argument.isString(key)) {
            throw new Exceptions_js_1.ObjectCastException(this.PREFIX_EXCEPTION + "The key must always be a string.");
        }
        if (Utils_js_1.UString.isEmpty(key)) {
            throw new Exceptions_js_1.NullPointerException(this.PREFIX_EXCEPTION + "You cannot add a new value if the key is null or empty.");
        }
        this.keys[this.size] = key;
        this.values[this.size] = value;
        this.ensureIncrementSize(1);
    }
    addIfAbsent(key, value) {
        let v = this.get(key);
        if (v == null) {
            this.add(key, value);
        }
    }
    addAll(...maps) {
        for (let map of maps) {
            if (!Argument_js_1.Argument.isOf(map, "LinkedMap")) {
                throw new Exceptions_js_1.ObjectCastException(this.PREFIX_EXCEPTION + map.constructor.name + " is not an instance of LinkedMap");
            }
            let size = map.length();
            if (size > 0) {
                for (let entry of map.entries()) {
                    this.add(entry.getKey(), entry.getValue());
                }
            }
        }
    }
    get(key) {
        let index = this.checkIfKeyExist(key);
        if (index >= 0) {
            for (let i = 0; i < this.size; i++) {
                if (index == i) {
                    return this.values[i];
                }
            }
        }
        return null;
    }
    set(key, value) {
        let index = this.checkIfKeyExist(key);
        if (index >= 0) {
            for (let i = 0; i < this.size; i++) {
                if (index == i) {
                    this.values[i] = value;
                    return;
                }
            }
        }
        else {
            throw new Exceptions_js_1.IllegalArgumentException(this.PREFIX_EXCEPTION + "The key `" + key + "` Does not exist in the map.");
        }
    }
    remove(key) {
        let index = this.checkIfKeyExist(key);
        if (index >= 0) {
            let otherKeys = [];
            let otherValues = [];
            for (let i = 0, k = 0, v = 0; i < this.size; i++) {
                if (index == i) {
                    continue;
                }
                otherKeys[k++] = this.keys[i];
                otherValues[v++] = this.values[i];
            }
            this.keys = otherKeys;
            this.values = otherValues;
            this.ensureDecrementSize(1);
            otherKeys = null;
            otherValues = null;
        }
        else {
            throw new Exceptions_js_1.IllegalArgumentException(this.PREFIX_EXCEPTION + "The key `" + key + "` Does not exist in the map.");
        }
    }
    clear() {
        for (let i = 0; i < this.size; i++) {
            this.keys[i] = null;
            this.values[i] = null;
        }
        this.size = 0;
        this.keys = [];
        this.values = [];
    }
    clearMemory() {
        this.keys.length = 0;
        this.values.length = 0;
        this.size = 0;
    }
    isEmpty() {
        return this.size == 0;
    }
    entries() {
        return new EntryIterable(this.keys, this.values, this.size);
    }
    reverse(type) {
        if (this.size > 0) {
            let otherKeys = [];
            let otherValues = [];
            for (let i = this.size - 1, k = 0, v = 0; i >= 0; i--) {
                otherKeys[k++] = this.keys[i];
                otherValues[v++] = this.values[i];
            }
            if (type == MapType.TYPE_KEYS) {
                this.keys = otherKeys;
            }
            else if (type == MapType.TYPE_VALUES) {
                this.values = otherValues;
            }
            else {
                this.keys = otherKeys;
                this.values = otherValues;
            }
            otherKeys = null;
            otherValues = null;
        }
    }
    toString() {
        let builder = new StringBuilder_js_1.StringBuilder();
        builder.append("[");
        for (let entry of this.entries()) {
            let key = entry.getKey();
            let value = entry.getValue();
            if (Argument_js_1.Argument.isPrimitive(value)) {
                builder.append(`"${key}" => "${value}"`).append((entry.isLastData() ? "" : ","));
            }
            else {
                builder.append(`"${key}" => "${value.constructor.name}"`).append((entry.isLastData() ? "" : ","));
            }
        }
        return builder.append("]").toString();
    }
    toJSON() {
        let builder = new StringBuilder_js_1.StringBuilder();
        builder.append("{");
        for (let entry of this.entries()) {
            let key = entry.getKey();
            let value = entry.getValue();
            if (!Argument_js_1.Argument.isPrimitive(value)) {
                throw new Exceptions_js_1.IllegalArgumentException(`${this.PREFIX_EXCEPTION}Your map contain an object. The JSON format can contain only primitive values [Float, Integer, Boolean, String].`);
            }
            if (Argument_js_1.Argument.isString(value)) {
                builder.append(`"${key}": "${value}"`).append((entry.isLastData() ? "" : ","));
            }
            else {
                builder.append(`"${key}": ${value}`).append((entry.isLastData() ? "" : ","));
            }
        }
        return builder.append("}").toString();
    }
    /**
     * Ensures the incrementation of the size to update the current size of the map
     *
     * @param value incremented value
     * @type {void}
     * @since 1.0
     */
    ensureIncrementSize(value) {
        this.size += value;
    }
    /**
     * Ensures the decrementation of the size to update the current size of the map
     *
     * @param value Decremented value
     * @type {void}
     * @since 1.0
     */
    ensureDecrementSize(value) {
        this.size -= value;
    }
    /**
     * Check if the key exist in the map and return the current index for get value
     *
     * @param key the key whose exist in the map is to be finded
     * @returns return the index of key
     * @type {number}
     * @since 1.0
     */
    checkIfKeyExist(key) {
        for (let i = 0; i < this.size; i++) {
            if (this.keys[i] == key) {
                return i;
            }
        }
        return -1;
    }
    /**
     * Return the index of the finded occurrence in the map
     * return -1 if the map does not contain the occurrence
     *
     * @param value value whose exist in the map is to be finded
     * @returns return the index of values
     * @type {number}
     * @since 1.0
     */
    indexOf(value) {
        for (let i = 0; i < this.size; i++) {
            if (this.values[i] == value) {
                return i;
            }
        }
        return -1;
    }
}
exports.LinkedMap = LinkedMap;
//# sourceMappingURL=LinkedMap.js.map